/*
 ============================================================================
 Name        : exame_2006_07_1a
 Author      : Agostinho
 Version     :
 Copyright   : Go & Learn
 Description : \SO\SO_09_10- Agostinho\Exames\Exames com algumas resolu�oes\2006-2007\1a chamada
 ============================================================================
 */

/* Para testar
 make escalonador
 ./escalonador.exe ./lino.exe ./duarte.exe ./vitor.exe ./agostinho.exe ./andre.exe ./rui.exe
 make clean
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>

// De modo a não ter que efectuar alocação dinâmica de arrays,
// vamos pré-estabelecer um número máximo de programas suportados,
// e assim fazer alocação estática dos arrays pidsDosFilhos e estadoExecucaoFilhos
#define MAX_PROGRAMS 10

#define TEMPO 3 // Segundos
// pidsDosFilhos
// pidsDosFilhos[0] = 6525
// pidsDosFilhos[1] = 6526
// pidsDosFilhos[2] = 6531
// ...
// Estrutura auxiliar para registar os pids dos filhos criados
int pidsDosFilhos[MAX_PROGRAMS];

// Estrutura auxiliar para registar os estado dos filhos criados
// 0 para terminado, 1 para a correr/suspenso
// estadoExecucaoFilhos
// estadoExecucaoFilhos[0] = 1 // está actualmente na pool de processos, que competem pelos recursos
// estadoExecucaoFilhos[1] = 0 // Já terminou a sua execução.
// estadoExecucaoFilhos[2] = 1 // idem (na pool)
int estadoExecucaoFilhos[MAX_PROGRAMS];

// indice do filho que está actualmente a correr.
// Se o escalonador estiver a lidar com 6 programas concorrentes, então este indice pode variar entre 0 e 5
int indiceFilhoActivo;

// Variável global, que detém o número de programas recebidos por argumento.
int numeroProgramas;

void switchChildProcess(int signo) {
	int pidFilhoActualmenteActivo = pidsDosFilhos[indiceFilhoActivo];

	// De modo a não darmos a volta à lista de filhos mais que uma vez, utilizamos esta variavel auxiliar
	int flagVoltaListaProgramas = 0;

	// Vamos procurar o proximo processo a quem dar o processo.
	// Indice ficará em indiceProximoFilhoActivo e flagFoundNextChild ficará a 1, se for encontado
	int flagFoundNextChild = 0;
	int indiceProximoFilhoActivo = indiceFilhoActivo; // Posicionar indiceProximoFilhoActivo no indice actual
	while (1) // o término do ciclo será feito manualmente, com recurso a break's
	{
		// iterar para o próximo indice
		indiceProximoFilhoActivo++;

		// vamos verificar se chegamos ao fim da lista, nesse caso teremos de nos reposicionar no inicio
		if (indiceProximoFilhoActivo >= numeroProgramas) {
			// Vamos verificar se já demos a volta antes
			if (flagVoltaListaProgramas == 0) {
				indiceProximoFilhoActivo = 0;
				flagVoltaListaProgramas = 1;
			} else {
				// Já demos a volta antes. Nao foi encontrado e assim flagFoundNextChild mantem-se a 0.
				break;
			}
		}

		if (estadoExecucaoFilhos[indiceProximoFilhoActivo] == 1) {
			flagFoundNextChild = 1;
			break; // nice, we've found the next child, and we'll give it the quantum right away
		} else {
			// Ok, bora ver o proximo. Bora iterar.
		}
	}
	// Exemplo:
	// No caso que:
	// 1: indiceFilhoActivo = 2
	// e
	// 2: estadoExecucaoFilhos está no seguinte estado
	// estadoExecucaoFilhos[0] = 0
	// estadoExecucaoFilhos[1] = 1
	// estadoExecucaoFilhos[2] = 1
	// estadoExecucaoFilhos[3] = 0
	// estadoExecucaoFilhos[4] = 1
	// Então, indiceProximoFilhoActivo será 0 4 (repare-se que o 3 está a 0 - já terminou)

	if (flagFoundNextChild == 1) {
		// Passar o "testemunho ao próximo" filho, e registar então o próximo indice activo
		indiceFilhoActivo = indiceProximoFilhoActivo;

		// Enviar sinal ao filho que está actualmente a correr, para suspender execução.
		kill(pidFilhoActualmenteActivo, SIGSTOP);

		// Enviar ao filho seguinte, sinal para continuar
		kill(pidsDosFilhos[indiceFilhoActivo], SIGCONT);
		printf("switchChildProcess: pidAntes: %d, pidDepois: %d\n",
				pidFilhoActualmenteActivo, pidsDosFilhos[indiceFilhoActivo]);
	} else {
		// Sem filhos a quem passar o testemunho:
		// Provavelmente, já terminaram todos
		// printf("INFO: pidAntes: %d\n", pidFilhoActualmenteActivo);
	}

	// Voltar a armadilhar o gajo
	signal(SIGALRM, switchChildProcess);
	// Voltar a pedir ao SO, para sinalizar o escalonador daque a TEMPO segundos
	alarm(TEMPO);
}

int main(int num_args, char *args[]) {

	// Definir o número de programas com que o escalonador vai lidar
	numeroProgramas = num_args - 1;

	// Inicializar a estrutura auxiliar 'acorrer', marcando que todos estão a correr (ainda q em execução ou suspensos)
	// Ficarão a 0, quando terminarem.
	for (int i = 0; i < numeroProgramas; i++) {
		// inicialmente estão todos os programas a concorrer pelos recursos, logo estão todos a 1
		estadoExecucaoFilhos[i] = 1;
	}

	// Obter o filho do pai, da app principal portanto, ou seja do escalonador :)
	// Variavel para acolher o valor do pid pai, da app esclanador
	int pid_pai = getpid();
	// printf("INFO: Pid escalonador: %d\n", pid_pai);

	// Lançar todos os filhos.
	// Internamente, todos à excepção do primeiro, serão suspensos
	printf("INFO: Vou criar %d filhos.\n", numeroProgramas);
	for (int i = 0; i < numeroProgramas; i++) {
		pid_t pid = fork(); //  creates  a new process by duplicating the calling process

		// Neste ponto, a àrvore de execução foi duplicada.
		// De modo a ver, se estamos no filho ou no pai, utilzámos a semântica do valor atribuido ao pid
		// que poderá ser -1 no caso de erro, 0 no caso do filho, ou o valor do pid relativo ao filho, no caso do pai

		switch (pid) {
		case -1: // Failure
			perror("FORK");
			exit(EXIT_FAILURE);

		case 0: // Child
			// The  exec() family of functions replaces the current process image with a new process image.
			printf("CHILD: [%s] My pid: %d\n", args[i + 1], getpid());
			execlp(args[i + 1], args[i + 1], NULL);

			// As duas seguintes linhas de código apenas serão executadas no caso da funcao execlp falhar.
			// E nesse caso, imprime o erro e sai com código de erro de seguida.
			// Caso suceda, dado que a imagem do processo foi substituida, o seguinte código não será considerado

			perror("ERROR ON EXEC");
			exit(EXIT_FAILURE);

		default: // Parent

			// pid: the PID of the child process is returned in the parent
			pidsDosFilhos[i] = pid; // Vamos registar o pid do filho, na estrutura pids
			printf("INFO: [%s] Child pid: %d\n", args[i + 1], pid);
			if (i == 0) {
				// marcar o primeiro programa, como o programa activo
				indiceFilhoActivo = 0;
			} else {
				// Para todos os sub processos, excepto para o primeiro,
				// enviar um sinal ao filho, a socilitar que suspenda a sua execução
				kill(pid, SIGSTOP);
			}
			break;
		}
	}

	// Vamos instruir o escalonador sobre o que fazer, quando receber o sinal SIGALRM
	signal(SIGALRM, switchChildProcess);

	// De seguida, vamos pedir ao sistema operativo para enviar um sinal SIGALRM, em TEMPO segundos
	// O sinal será enviado para este processo pai, e o programa vai executar a funcao corre_alr, conforme foi instruido anteriormente
	alarm(TEMPO);

	// variável manter-se-á a 1, enquanto houver um filho activo.
	int existemFilhosActivos = 1;

	// Variável para registar o numero de filhos que já terminaram,
	// de modo a poder saber quando já não restam quaisquer filho activo
	int numeroProgramasTerminados = 0;

	while (existemFilhosActivos == 1) {

		// Variavel para utilizar com a função wait
		int status = 0;

		// Quanto ao status, repare-se que é passado o endereço &status, pelo que a função poderá escrever na variável
		// Variavel para receber o valor devolvido pela função wait
		// Semantica do valor returnado pela função wait:
		// on success, returns the process ID of the terminated child; on
		//        error, -1 is returned.
		int pidReturn = wait(&status);

		if (pidReturn == -1) {
			// perror("ERROR ON wait");

		} else {
			// Terminou mais um filhote
			numeroProgramasTerminados++;

			if (WIFEXITED(status))
			// WIFEXITED(status)
			// returns true if the child terminated normally,
			// that is, by call‐ing exit(3) or _exit(2), or by returning from main().
			{
				printf("INFO: Filho com pid %d terminou normalmente.\n",
						pidReturn);
			} else {
				printf("INFO: Filho com pid %d terminou com erro.\n",
						pidReturn);
			}

			// Marcar o filho que estava actualmente a correr, como inactivo
			// Note-se que na presente configuração do escalonador, apenas corre um programa ao mesmo tempo
			// pelo que o unico programa que poderá ter terminado é exactamente esse, o que está a correr
			// Assim é válido assumir, que o filho que terminou é o que correspondente ao indiceFilhoActivo
			estadoExecucaoFilhos[indiceFilhoActivo] = 0;
			if (pidsDosFilhos[indiceFilhoActivo] != pidReturn) {
				printf(stderr, "ATENACAO: Situação não esperada: %d != %d\n",
						pidsDosFilhos[indiceFilhoActivo], pidReturn);
			}

			// Enviar um sinal de alarme, para que o escalonador passa imediatamente a vez a outro
			kill(pid_pai, SIGALRM);
		}

		if (numeroProgramasTerminados == numeroProgramas) {
			// marcar condição de termino do presente ciclo como false, de modo a terminar o ciclo while,
			// e consequentemente terminar o escalonador
			existemFilhosActivos = 0;
		}
	}

	// Great JOB. Well Done. :P
	return EXIT_SUCCESS;
}
